package durst.data;

import java.sql.SQLException;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.apache.log4j.Logger;

import com.j256.ormlite.dao.Dao;
import com.j256.ormlite.dao.ForeignCollection;
import com.j256.ormlite.field.DatabaseField;
import com.j256.ormlite.field.ForeignCollectionField;
import com.j256.ormlite.table.DatabaseTable;
import com.sdicons.json.mapper.JSONMapper;

import durst.data.dao.DaoFactory;
import durst.data.json.EventJSONHelper;

@DatabaseTable(tableName = "Event")
public class Event extends CSDEntity implements Comparable<Event> {
	static {
		JSONMapper.addHelper(new EventJSONHelper());
	}

	public enum RepeatFrequency {
		NONE(Calendar.DATE, 0, false) {
			@Override
			public List<Event> getRepeats(Event clone, Calendar start, Calendar end) {
				return new LinkedList<Event>();
			}
		},
		EVERY_DAY(Calendar.DATE, 1, false), WORK_DAILY(Calendar.DATE, 1, false) {
			@Override
			boolean decrementCheck(Calendar cal) {
				return cal.get(Calendar.DAY_OF_WEEK) == Calendar.SATURDAY
						|| cal.get(Calendar.DAY_OF_WEEK) == Calendar.SUNDAY;
			}
		},
		WEEKLY(Calendar.DATE, 7, false), MONTHLY_ABS(Calendar.MONTH, 1, false), MONTHLY_REL(
				Calendar.WEEK_OF_MONTH, 4, true), YEARLY_ABS(Calendar.YEAR, 1, false), YEARLY_REL(
				Calendar.WEEK_OF_MONTH, 52, false);

		private int increment;
		private int quantity;
		private boolean relative;

		boolean decrementCheck(Calendar cal) {
			return false;
		}

		private RepeatFrequency(int incr, int quant, boolean relative) {
			this.increment = incr;
			this.quantity = quant;
			this.relative = relative;
		}

		public List<Event> getRepeats(Event clone, Calendar start, Calendar end) {
			List<Event> ret = new LinkedList<Event>();
			Calendar cal = Calendar.getInstance();
			cal.setTime(clone.getStartTime());

			Calendar dNR = Calendar.getInstance();
			dNR.setTime(clone.getDoNotRepeatPast());

			if (!relative)
				while (!cal.after(end) && !cal.after(dNR)) {
					cal.add(increment, quantity);
					if (cal.after(start) && !cal.after(dNR) && !decrementCheck(cal))
						ret.add(createEvent(cal, clone));
				}
			else {
				Calendar rel = Calendar.getInstance();
				rel.setTime(clone.getStartTime());

				while (!cal.after(end) && !cal.after(dNR)) {
					cal.add(increment, quantity);
					while (cal.get(Calendar.DAY_OF_WEEK) != rel.get(Calendar.DAY_OF_WEEK)
							|| cal.get(Calendar.WEEK_OF_MONTH) != rel.get(Calendar.WEEK_OF_MONTH))
						cal.add(Calendar.DATE, 1);
					if (cal.after(start) && !cal.after(dNR) && !decrementCheck(cal))
						ret.add(createEvent(cal, clone));
				}
			}

			return ret;
		}

		private static Event createEvent(Calendar clone, Event event) {
			Calendar shiftedStart = (Calendar) clone.clone();
			Calendar clonedStart = Calendar.getInstance();
			clonedStart.setTime(event.startTime);

			shiftedStart.set(Calendar.HOUR_OF_DAY, clonedStart.get(Calendar.HOUR_OF_DAY));
			shiftedStart.set(Calendar.MINUTE, clonedStart.get(Calendar.MINUTE));
			shiftedStart.set(Calendar.MILLISECOND, clonedStart.get(Calendar.MILLISECOND));

			Calendar shiftedEnd = (Calendar) clone.clone();
			Calendar clonedEnd = Calendar.getInstance();
			clonedEnd.setTime(event.endTime);

			shiftedEnd.set(Calendar.HOUR_OF_DAY, clonedEnd.get(Calendar.HOUR_OF_DAY));
			shiftedEnd.set(Calendar.MINUTE, clonedEnd.get(Calendar.MINUTE));
			shiftedEnd.set(Calendar.MILLISECOND, clonedEnd.get(Calendar.MILLISECOND));

			Event ret = new Event(event.getId(), event.getParent(), event.getDescription(),
					shiftedStart.getTime(), shiftedEnd.getTime(), true, event.getDoNotRepeatPast(),
					NONE, event.getTitle());
			return ret;
		}
	}

	public enum Range {
		BEFORE, INSIDE, AFTER
	}

	@DatabaseField(generatedId = true)
	private int id;

	@DatabaseField(canBeNull = false, foreign = true)
	private UserCalendar parent;

	@DatabaseField(canBeNull = false)
	private String description;

	@DatabaseField(canBeNull = false)
	private Date startTime;

	@DatabaseField(canBeNull = false)
	private Date endTime;

	@DatabaseField(canBeNull = false)
	private boolean repeated;

	@DatabaseField(canBeNull = false, unknownEnumName = "NONE")
	private RepeatFrequency frequency;

	@DatabaseField
	private Date doNotRepeatPast;

	@DatabaseField
	private String title;

	public Event() {
	}

	public Event(int id, UserCalendar parent, String description, Date start, Date end,
			boolean repeated, Date dNR, RepeatFrequency freq, String title) {
		this.id = id;
		this.parent = parent;
		this.description = description;
		this.startTime = start;
		this.endTime = end;
		this.repeated = repeated;
		this.frequency = freq;
		this.doNotRepeatPast = dNR;
		this.title = title;
	}

	public Range eventInRange(Calendar start, Calendar end) {
		return startTime.after(end.getTime()) ? Range.AFTER : startTime.after(start.getTime())
				&& startTime.before(end.getTime()) || end != null && endTime.after(start.getTime())
				&& endTime.before(end.getTime()) ? Range.INSIDE : Range.BEFORE;
	}

	public static List<Event> eventsInRange(List<Event> events, Calendar start, Calendar end) {
		List<Event> ret = new LinkedList<Event>();

		Range last = Range.BEFORE;
		for (int ndx = 0; ndx < events.size() && last.ordinal() <= Range.INSIDE.ordinal(); ndx++) {
			last = events.get(ndx).eventInRange(start, end);
			if (last == Range.INSIDE)
				ret.add(events.get(ndx));
		}
		return ret;
	}

	@Override
	public int hashCode() {
		return id;
	}

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public UserCalendar getParent() {
		return parent;
	}

	public void setParent(UserCalendar parent) {
		this.parent = parent;
	}

	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Date getStartTime() {
		return startTime;
	}

	public void setStartTime(Date startTime) {
		this.startTime = startTime;
	}

	public Date getEndTime() {
		return endTime;
	}

	public void setEndTime(Date endTime) {
		this.endTime = endTime;
	}

	public void setRepeated(boolean repeated) {
		this.repeated = repeated;
	}

	public boolean isRepeated() {
		return repeated;
	}

	public void setFrequency(RepeatFrequency frequency) {
		this.frequency = frequency;
	}

	public RepeatFrequency getFrequency() {
		return frequency;
	}

	@Override
	public int compareTo(Event other) {
		if (startTime.before(other.startTime))
			return -1;
		if (startTime.after(other.startTime))
			return 1;
		if (endTime.before(other.endTime))
			return -1;
		if (endTime.after(other.endTime))
			return 1;
		return 0;
	}

	public void setDoNotRepeatPast(Date doNotRepeatPast) {
		this.doNotRepeatPast = doNotRepeatPast;
	}

	public Date getDoNotRepeatPast() {
		return doNotRepeatPast;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public String getTitle() {
		return title;
	}

	public List<Invite> getInvites() {
		try {
			Dao<Invite, Integer> dao = DaoFactory.getInstance().createDao(Invite.class);
			return dao.queryBuilder().where().eq("event_id", id).query();
		}
		catch (SQLException err) {
			Logger.getLogger(Event.class).error("SQLException in getInvites()" + err.getMessage());
			return new LinkedList<Invite>();
		}
	}

	public List<FileAccess> getFiles() {
		try {
			Dao<FileAccess, Integer> dao = DaoFactory.getInstance().createDao(FileAccess.class);
			return dao.queryBuilder().where().eq("event_id", id).query();
		}
		catch (SQLException err) {
			Logger.getLogger(Event.class).error("SQLException in getFiles()" + err.getMessage());
			return new LinkedList<FileAccess>();
		}
	}
}
